"""
债券回测系统RESTful API
提供债券回测的HTTP接口服务
"""
import os
import sys
import json
import time
import uuid
from datetime import datetime, timedelta
import pandas as pd
import numpy as np
from flask import Flask, request, jsonify, send_file, make_response
from flask_restful import Resource, reqparse, Api
import io
from loguru import logger
from flask_cors import CORS
from database.mysqldb import MysqlDatabase
from engine.engine import BacktestEngine
from engine.constant import OffsetType
from strategy.examples.bond_target_position import BondTargetPositionStrategy
from visualization.performance import PerformanceAnalyzer, PerformanceVisualizer
from database.mysqldb import InInfIdpBndInfoDi,dbpool
# 添加项目根目录到路径
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(project_root)



# 配置日志
logger.remove()
logger.add(
    os.path.join(project_root, "logs", "api.log"),
    level="INFO",
    rotation="10 MB",
    compression="zip",
    enqueue=True
)

class LoguruHandler:
    """日志处理类，捕获回测过程中的日志"""
    def __init__(self):
        self.messages = []
    
    def write(self, message):
        message = message.strip()
        if message:
            self.messages.append(message)
            # 限制消息数量，保留最新的5000条
            if len(self.messages) > 5000:
                self.messages.pop(0)
    
    def get_logs(self):
        return self.messages.copy()

# 创建日志处理器实例
log_handler = LoguruHandler()

# 重定向loguru日志到处理器
class InterceptHandler:
    def __init__(self, log_handler):
        self.log_handler = log_handler
    
    def write(self, message):
        self.log_handler.write(message)
    
    def flush(self):
        pass

# 添加自定义处理器到loguru
logger.add(InterceptHandler(log_handler), level="INFO")

# 初始化Flask应用
app = Flask(__name__)
CORS(app)  # 添加CORS支持，允许跨域请求
api = Api(app)
@app.teardown_request
def teardown_request(exception):
    if not dbpool.is_closed():
        dbpool.close()
@app.before_request
def before_request():
    if dbpool.is_closed():
        dbpool.connect()

def run_backtest(target_positions, start_date, end_date, symbols=None):
    """
    执行回测
    
    Args:
        target_positions: 目标仓位数据，格式为{日期: {债券代码: 权重}}
        start_date: 开始日期
        end_date: 结束日期
        symbols: 回测标的代码，可为逗号分隔的字符串，如不提供则使用target_positions中的所有标的
    
    Returns:
        回测结果
    """
    # 清空之前的日志记录
    log_handler.messages = []
    
    # 记录回测开始信息
    logger.info(f"开始回测: {start_date} 至 {end_date}")
    logger.info(f"目标仓位记录数: {len(target_positions)}")
    logger.info(f"目标仓位清单：{target_positions}")
    
    # 如果未提供symbols，从target_positions中提取
    if not symbols:
        all_symbols = set()
        for date_pos in target_positions.values():
            all_symbols.update(date_pos.keys())
        symbols = ','.join(all_symbols)
    
    logger.info(f"回测标的: {symbols}")
    
    # 将日期格式化为YYYYMMDD字符串格式
    start_str = start_date.strftime('%Y%m%d') if isinstance(start_date, datetime) else start_date.replace('-', '')
    end_str = end_date.strftime('%Y%m%d') if isinstance(end_date, datetime) else end_date.replace('-', '')
    
    # 创建数据库连接和回测引擎
    
    # 创建回测引擎，传入回测区间和数据库实例
    database = MysqlDatabase()  # 创建MysqlDatabase实例
    engine = BacktestEngine(
        start=start_str,
        end=end_str,
        offset=OffsetType.DAY,
        initial_capital=1000000,  # 初始资金100万
        database=database
    )
    
    # 添加回测标的资产（会自动从数据库构建资产对象）
    engine.add_asset(symbols)
    
    # 加载市场数据（不需要传入参数，会自动从数据库加载已添加资产的市场数据）
    engine.add_market_data()
    
    # 记录资产和市场数据信息
    logger.info(f"添加资产: {symbols}")
    logger.info(f"市场数据时间范围: {start_date} 至 {end_date}")
    
    # 创建并设置策略
    strategy = BondTargetPositionStrategy("债券目标仓位策略", target_positions,
                                         start_date, end_date,
                                        portfolio_flag=True)
    engine.set_strategy(strategy)
    
    # 执行回测
    logger.info("开始执行回测...")
    backtest_results = engine.run_backtest()
    logger.info("回测执行完成！")
    
    # # 获取实际的回测日期范围（根据市场数据）
    # actual_dates = sorted(set.union(*[
    #     set(data.keys()) for data in engine.market_data.values()
    # ]))
    # actual_start_date = actual_dates[0] if actual_dates else start_date
    # actual_end_date = actual_dates[-1] if actual_dates else end_date
    
    # logger.info(f"实际回测时间范围: {actual_start_date} 至 {actual_end_date}")
    
    # # 提取回测结果
    # results = {
    #     'equity_curve': pd.DataFrame({
    #         'total_equity': engine.equity_curve,
    #         'date': actual_dates  # 使用实际的市场数据日期，而不是生成的日期序列
    #     }).set_index('date'),
    #     'trades': engine.trades
    # }
    
    # # 使用PerformanceAnalyzer计算性能指标
    # backtest_df = pd.DataFrame({
    #     'date': actual_dates,
    #     'total_value': engine.equity_curve,
    #     'weights': [engine.position.get_position_weights() for _ in actual_dates],
    #     'duration': [0.0] * len(actual_dates),  # 添加duration字段，默认为0
    #     'YTM': [0.0] * len(actual_dates)  # 添加YTM字段，默认为0
    # }).set_index('date')
    
    # # 计算收益率
    # backtest_df['returns'] = backtest_df['total_value'].pct_change().fillna(0)
    
    # 使用PerformanceAnalyzer计算指标
    analyzer = PerformanceAnalyzer(backtest_results)
    indicators = analyzer.metrics
    
    # 记录回测结果摘要
    logger.info(f"回测结果摘要:")
    logger.info(f"- 总收益率: {indicators['total_return']*100:.2f}%")
    logger.info(f"- 年化收益率: {indicators['annualized_return']*100:.2f}%")
    logger.info(f"- 最大回撤: {indicators['max_drawdown']*100:.2f}%")
    logger.info(f"- 夏普比率: {indicators['sharpe_ratio']:.2f}")
    
    # 进一步简化返回结构
    return {
        "backtest_results": backtest_results,  # 原始回测结果DataFrame
        "logs": log_handler.get_logs()
    }



def get_report_list():
    """
    获取tests/reports目录下的所有回测报告
    
    Returns:
        报告文件列表，按时间倒序排序
    """
    # 修改为从tests/reports目录获取报告
    report_dir = os.path.join(project_root, "tests", "reports")
    if not os.path.exists(report_dir):
        return []
    
    report_files = []
    for file in os.listdir(report_dir):
        if file.startswith("backtest_report_") and file.endswith(".html"):
            file_path = os.path.join(report_dir, file)
            # 获取文件创建时间
            creation_time = os.path.getctime(file_path)
            report_files.append({
                "filename": file,
                "path": file_path,
                "creation_time": creation_time,
                "creation_date": datetime.fromtimestamp(creation_time).strftime("%Y-%m-%d %H:%M:%S")
            })
    
    # 按创建时间倒序排序
    report_files.sort(key=lambda x: x["creation_time"], reverse=True)
    return report_files

class BacktestResource(Resource):
    """回测接口资源"""
    def post(self):
        """
        执行回测API
        
        Request JSON格式:
        {
            "start_date": "2023-01-01",
            "end_date": "2023-12-31",
            "symbols": "110001,110002,110003",  // 可选参数，回测标的代码，英文逗号分隔
            "target_positions": [
                {
                    "date": "2023-01-15",
                    "weights": {"110001": 0.33, "110002": 0.33, "110003": 0.34}
                },
                ...
            ]
        }
        
        Response:
        {
            "success": true,
            "data": {
                "report_id": "backtest_report_20230101_120000.html",
                "report_url": "/api/bondbacktest/reports/backtest_report_20230101_120000.html",
                "performance_metrics": {...}
            }
        }
        """
        try:
            data = request.get_json()
            
            # 验证请求数据
            if not all(key in data for key in ["start_date", "end_date", "target_positions"]):
                return {"success": False, "error": "请求数据格式不正确，必须包含start_date, end_date和target_positions字段"}, 400
            
            # 解析日期
            try:
                start_date = datetime.strptime(data["start_date"], "%Y-%m-%d")
                end_date = datetime.strptime(data["end_date"], "%Y-%m-%d")
            except ValueError:
                return {"success": False, "error": "日期格式不正确，应为YYYY-MM-DD格式"}, 400
            
            # 获取标的代码（可选参数）
            symbols = data.get("symbols", None)
            
            # 验证并转换目标仓位数据
            target_positions = {}
            for pos in data["target_positions"]:
                if "date" not in pos or "weights" not in pos:
                    return {"success": False, "error": "目标仓位数据格式不正确，应包含date和weights字段"}, 400
                
                try:
                    date = datetime.strptime(pos["date"], "%Y-%m-%d")
                    weights = pos["weights"]
                    
                    # 验证权重和为1
                    if abs(sum(weights.values()) - 1.0) > 0.01:
                        return {"success": False, "error": f"目标仓位{pos['date']}的权重和应为1.0"}, 400
                    
                    target_positions[date] = weights
                except ValueError:
                    return {"success": False, "error": f"目标仓位日期{pos['date']}格式不正确，应为YYYY-MM-DD格式"}, 400
            
            # 执行回测，传入可选的标的代码参数
            backtest_results = run_backtest(target_positions, start_date, end_date, symbols)
            
            # 生成报告文件名（使用时间戳确保唯一性）
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            report_filename = f"backtest_report_{timestamp}.html"
            # 修改报告生成路径为tests/reports目录
            tests_reports_dir = os.path.join(project_root, "tests", "reports")
            # 确保目录存在
            os.makedirs(tests_reports_dir, exist_ok=True)
            report_path = os.path.join(tests_reports_dir, report_filename)
            
            # 使用PerformanceVisualizer生成报告
            analyzer = PerformanceAnalyzer(backtest_results["backtest_results"])
            visualizer = PerformanceVisualizer(analyzer)
            
            # 获取性能指标
            performance_metrics = analyzer.metrics
            
            # 生成报告
            visualizer.export_html_report(report_path)
            
            # 构建响应数据
            response_data = {
                "success": True,
                "data": {
                    "report_id": report_filename,
                    "report_url": f"/api/bondbacktest/reports/{report_filename}",
                    "performance_metrics": {
                        "total_return": float(performance_metrics['total_return']),
                        "annual_return": float(performance_metrics['annualized_return']),
                        "sharpe_ratio": float(performance_metrics['sharpe_ratio']),
                        "max_drawdown": float(performance_metrics['max_drawdown']),
                        "volatility": float(performance_metrics['volatility']),
                        "sortino_ratio": float(performance_metrics.get('sortino_ratio', 0)),
                        "calmar_ratio": float(performance_metrics.get('calmar_ratio', 0)),
                        "turnover_rate": float(performance_metrics.get('turnover_rate', 0)),
                        "avg_holdings_count": float(performance_metrics.get('avg_holdings_count', 0))
                    },
                    "logs": backtest_results["logs"]
                }
            }
            
            return response_data
            
        except Exception as e:
            logger.exception("回测执行失败")
            return {"success": False, "error": f"回测执行失败: {str(e)}"}, 500

class ReportListResource(Resource):
    """报告列表接口资源"""
    def get(self):
        """
        获取所有回测报告列表
        
        Response:
        {
            "success": true,
            "data": [
                {
                    "id": "backtest_report_20230101_120000.html",
                    "url": "/api/bondbacktest/reports/backtest_report_20230101_120000.html",
                    "creation_date": "2023-01-01 12:00:00"
                },
                ...
            ]
        }
        """
        try:
            reports = get_report_list()
            
            response_data = {
                "success": True,
                "data": [
                    {
                        "id": report["filename"],
                        "url": f"/api/bondbacktest/reports/{report['filename']}",
                        "creation_date": report["creation_date"]
                    }
                    for report in reports
                ]
            }
            
            return response_data
        
        except Exception as e:
            logger.exception("获取报告列表失败")
            return {"success": False, "error": f"获取报告列表失败: {str(e)}"}, 500

class ReportResource(Resource):
    """报告文件接口资源"""
    def get(self, report_id):
        """
        获取指定报告文件
        """
        # 修改为从tests/reports目录获取报告
        report_path = os.path.join(project_root, "tests", "reports", report_id)
        
        if not os.path.exists(report_path):
            return {"success": False, "error": "报告文件不存在"}, 404
        
        # 使用自定义的响应而不是直接返回send_file，这样可以添加必要的头信息
        response = make_response(send_file(report_path, mimetype='text/html'))
        
        # 添加必要的头信息，以确保iframe可以正确加载内容
        response.headers['X-Frame-Options'] = 'SAMEORIGIN'
        response.headers['Content-Security-Policy'] = "frame-ancestors 'self' localhost:* 127.0.0.1:*"
        response.headers['Access-Control-Allow-Origin'] = '*'
        response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
        response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
        
        # 增强缓存控制，避免返回304
        response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate, max-age=0'
        response.headers['Pragma'] = 'no-cache'
        response.headers['Expires'] = '-1'
        response.headers['Last-Modified'] = datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT')
        response.headers['ETag'] = f'"{uuid.uuid4().hex}"'  # 使用随机ETag强制重新加载
        
        return response
    
    def delete(self, report_id):
        """
        删除指定报告文件
        """
        # 修改为从tests/reports目录删除报告
        report_path = os.path.join(project_root, "tests", "reports", report_id)
        
        if not os.path.exists(report_path):
            return {"success": False, "error": "报告文件不存在"}, 404
        
        try:
            os.remove(report_path)
            return {"success": True, "message": f"报告{report_id}已删除"}
        except Exception as e:
            logger.exception(f"删除报告{report_id}失败")
            return {"success": False, "error": f"删除报告失败: {str(e)}"}, 500

class BondSymbolSearch(Resource):
    def get(self):
        try:
            # 获取查询参数
            parser = reqparse.RequestParser()
            parser.add_argument('query', type=str, location='args')
            args = parser.parse_args()
            
            query = args['query'] if args['query'] else ''
            
            # 使用 peewee 模型进行查询，替代原来的 SQL 查询字符串
            query_results = InInfIdpBndInfoDi.select(
                InInfIdpBndInfoDi.tradingcode.alias('symbol'),
                InInfIdpBndInfoDi.secuabbr.alias('name'),
                InInfIdpBndInfoDi.coup_face_intr.alias('couponrate'),
                InInfIdpBndInfoDi.enddate.alias('maturitydate')
            ).where(
                (InInfIdpBndInfoDi.tradingcode.contains(query)) | 
                (InInfIdpBndInfoDi.secuabbr.contains(query))
            ).order_by(
                InInfIdpBndInfoDi.tradingcode
            ).limit(20).dicts()
            
            # 转换查询结果为列表
            results = list(query_results)
            
            # 格式化日期
            for item in results:
                if 'maturitydate' in item and item['maturitydate']:
                    item['maturitydate'] = item['maturitydate'].strftime('%Y-%m-%d')
            
            return {
                'success': True,
                'data': results
            }
            
        except Exception as e:
            logger.error(f"债券查询出错: {str(e)}")
            return {
                'success': False,
                'error': f"查询出错: {str(e)}"
            }, 500
            


# 注册API资源
api.add_resource(BacktestResource, '/api/bondbacktest/backtest')
api.add_resource(ReportListResource, '/api/bondbacktest/reports')
api.add_resource(ReportResource, '/api/bondbacktest/reports/<string:report_id>')
api.add_resource(BondSymbolSearch, '/api/bondbacktest/bond/symbol/search')

# 根路径API文档（简洁版）
@app.route('/')
def index():
    return jsonify({
        "name": "债券回测系统API",
        "version": "1.0.0",
        "description": "债券回测系统RESTful API服务",
        "endpoints": {
            "/api/bondbacktest/backtest": "执行债券回测",
            "/api/bondbacktest/reports": "获取回测报告列表", 
            "/api/bondbacktest/reports/<report_id>": "获取或删除指定回测报告",
            "/api/bondbacktest/bond/symbol/search": "模糊查询债券标的"
        }
    })

# 运行应用
if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=5000)
